/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: cbus_message_manager.c
 *
 * Purpose: implementation message manager for cbus endpoint.
 *
 * Developer:
 *   wen.gu , 2022-01-18
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "cbus/bus/cbus_message_manager.h"

#include "cbus/core/cbus_map.h"
#include "cbus/core/cbus_log.h"
/******************************************************************************
 **    MACROS
 ******************************************************************************/

/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

typedef struct _cbus_message_category
{
    /** a category message( with the same message type) just use one callback function 
     *  if thie category callback is set, then will be not use 'message_handler_map' when received message
     */
    cbus_message_handler_t category_handler;

    /** every one message have this own callback function, like <message id, msg_handler> */
    cbus_map_t message_handler_map;  
}cbus_message_category_t;


struct _cbus_message_manager
{
    cbus_map_t message_map;

};

/******************************************************************************
 **    inner FUNCTION DEFINITIONS
 ******************************************************************************/

static void cbus_message_category_destroy(void* ptr)
{
    cbus_message_category_t* msg_cat = (cbus_message_category_t*)ptr;
    cbus_map_destroy(msg_cat->message_handler_map);
    free(ptr);
}

static cbus_message_category_t* cbus_message_category_create()
{
    cbus_message_category_t* msg_cat = (cbus_message_category_t*)malloc(sizeof(cbus_message_category_t));

    if (msg_cat)
    {
        msg_cat->message_handler_map = cbus_map_create(CMV_TYPE_Pointer, CBUS_MAP_DEFAULT_CAPACITY);

        if (!msg_cat->message_handler_map)
        {
            free(msg_cat);
            return NULL;
        }

        msg_cat->category_handler.opaque = NULL;
        msg_cat->category_handler.on_message = NULL;
    }

    return msg_cat;
}


static GErrc cbus_message_manager_add_handler(cbus_map_t message_handler_map, GU32 message_id, cbus_message_on_process_func on_message, GPTR opaque)
{
    cbus_message_handler_t* msg_handler = (cbus_message_handler_t*)malloc(sizeof(cbus_message_handler_t));

    if (!msg_handler)
    {
        return G_ErrInsufficientResources;
    }

    msg_handler->on_message = on_message;
    msg_handler->opaque     = opaque;

    return (cbus_map_set_pointer(message_handler_map, message_id, (const void*)msg_handler, free) == GTRUE) ? G_OK : G_ErrUndefined;
}

/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

cbus_message_manager_t cbus_message_manager_create()
{

    /** here is a map group, like
     * map<msg_type, msg_id_map>
     * key:  message type  
     * value: a map to map message id and message porcess handler, like: <msg_id, msg_handler>
     *        - key: message id
     *        - value: message porcess handler 
     * 
     */  
    return (cbus_message_manager_t)cbus_map_create(CMV_TYPE_Pointer, CBUS_MAP_DEFAULT_CAPACITY);
}

void cbus_message_manager_destroy(cbus_message_manager_t manager)
{
    cbus_map_destroy((cbus_map_t)manager);
}

GErrc cbus_message_manager_register(cbus_message_manager_t manager, const cbus_message_info_t* msg_info)
{
    if (!manager || !msg_info || !msg_info->on_message)
    {
        return G_ErrBadParameter;
    }

    cbus_map_t message_handler_map = (cbus_map_t)manager;

    cbus_message_category_t* msg_cat = NULL;
    GBOL do_cat_create = GFALSE;
    if (cbus_map_find_pointer(message_handler_map, msg_info->message_type, (const void**)&msg_cat) == GFALSE)
    { /** if this message type not create 'cbus_message_category_t' before, then create it. */
        do_cat_create = GTRUE;
        msg_cat = cbus_message_category_create();

        if (!msg_cat)
        {
            return G_ErrInsufficientResources;
        }
    }

    if (cbus_message_manager_add_handler(msg_cat->message_handler_map, msg_info->message_id, msg_info->on_message, msg_info->opaque) != G_OK)
    {
        if (do_cat_create == GTRUE)
        {
            cbus_message_category_destroy(msg_cat);
        }        
        return G_ErrUndefined;            
    }   

    if (cbus_map_set_pointer(message_handler_map, msg_info->message_type, msg_cat, cbus_message_category_destroy) == GFALSE)
    {
        if (do_cat_create == GTRUE)
        {
            cbus_message_category_destroy(msg_cat);
        }
        return G_ErrUndefined;
    }

    return G_OK;
}

GErrc cbus_message_manager_register_group(cbus_message_manager_t manager, const cbus_message_group_t* group)
{
    if (!manager || !group || !(group->on_message) || !(group->message_ids))
    {
        return G_ErrBadParameter;
    }

    GU32* msg_ids = group->message_ids;
    GU08 msg_type = group->message_type;
    cbus_message_on_process_func on_message = group->on_message;
    GPTR opaque = group->opaque;
    cbus_message_info_t msg_info;
    
    msg_info.message_type = group->message_type;
    msg_info.on_message = group->on_message;
    msg_info.opaque = group->opaque;

    for (size_t i = 0; i < group->size; i++)
    {
        msg_info.message_id = msg_ids[i];
        GErrc ret = cbus_message_manager_register(manager, &msg_info);

        if (G_OK != ret)
        {
            LOGE("register msg(%d), type(%d) failed(0x%08x)\n", msg_ids[i], msg_type, ret);
            return ret; /** todo, is need ignore error */
        }
    }
    
    return G_OK;
}

GErrc cbus_message_manager_register_category(cbus_message_manager_t manager, cbus_message_type_t message_type, cbus_message_on_process_func on_message, GPTR opaque)
{
    if (!manager || !on_message)
    {
        return G_ErrBadParameter;
    }

    cbus_map_t message_handler_map = (cbus_map_t)manager;

    cbus_message_category_t* msg_cat = NULL;

    if (cbus_map_find_pointer(message_handler_map, message_type, (const void**)&msg_cat) == GFALSE)
    { /** if this message type not create 'cbus_message_category_t' before, then create it. */

        msg_cat = cbus_message_category_create();

        if (!msg_cat)
        {
            return G_ErrInsufficientResources;
        }

        if (cbus_map_set_pointer(message_handler_map, message_type, msg_cat, cbus_message_category_destroy) == GFALSE)
        {
            cbus_message_category_destroy(msg_cat);
            return G_ErrUndefined;
        }
    }   

    msg_cat->category_handler.on_message = on_message;
    msg_cat->category_handler.opaque     = opaque;

    return G_OK;
}

GErrc cbus_message_manager_unregister(cbus_message_manager_t manager, cbus_message_type_t message_type, cbus_message_id  message_id)
{
    if (!manager)
    {
        return G_ErrBadParameter;
    }

    cbus_message_category_t* msg_cat = NULL;

    if (cbus_map_find_pointer((cbus_map_t)manager, message_type, (const void**)&msg_cat) == GFALSE)
    { 
        return G_ErrNotFound;
    }

    if (cbus_map_remove(msg_cat->message_handler_map, message_id) == GFALSE)
    {
        return G_ErrUndefined;
    }

    return G_OK;
}

GErrc cbus_message_manager_unregister_category(cbus_message_manager_t manager, cbus_message_type_t message_type)
{
    if (!manager)
    {
        return G_ErrBadParameter;
    }

    return (cbus_map_remove((cbus_map_t)manager, message_type) == GTRUE) ? G_OK : G_ErrNotFound;
}

GErrc cbus_message_manager_unregister_all(cbus_message_manager_t manager)
{
   if (!manager)
    {
        return G_ErrBadParameter;
    }

    cbus_map_clear((cbus_map_t)manager);

    return G_OK;
}

const cbus_message_handler_t* cbus_message_manager_get_hander(cbus_message_manager_t manager, cbus_message_type_t message_type, cbus_message_id message_id)
{
    if (!manager)
    {
        return NULL;
    }

    cbus_message_category_t* msg_cat = NULL;
    cbus_message_handler_t* handler = NULL;

    if (cbus_map_find_pointer((cbus_map_t)manager, message_type, (const void**)&msg_cat) == GTRUE)
    {
        if (msg_cat->category_handler.on_message)
        {
            return &msg_cat->category_handler;
        }

        cbus_map_find_pointer(msg_cat->message_handler_map, message_id, (const void**)&handler);
    }

    return handler;
}
